// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 1992, 1998-2006 Linus Torvalds, Ingo Molnar
 * Copyright (C) 2005-2006, Thomas Gleixner, Russell King
 *
 * This file contains the core interrupt handling code, for irq-chip based
 * architectures. Detailed information is available in
 * Documentation/core-api/genericirq.rst
 */
#include <seminix/smp.h>
#include <seminix/irqdomain.h>
#include <seminix/irq.h>
#include <seminix/irqdesc.h>
#include <seminix/irq/dummychip.h>
#include <seminix/irq/chip.h>
#include <seminix/irq/handle.h>
#include <seminix/irq/manage.h>
#include "internal.h"

static irqreturn_t bad_chained_irq(int irq, void *dev_id)
{
    WARN_ONCE(1, "Chained irq %d should not call an action\n", irq);
    return IRQ_NONE;
}

/*
 * Chained handlers should never call action on their IRQ. This default
 * action will emit warning if such thing happens.
 */
struct irqaction chained_action = {
    .handler = bad_chained_irq,
};

/**
 *	irq_set_chip - set the irq chip for an irq
 *	@irq:	irq number
 *	@chip:	pointer to irq chip description structure
 */
int irq_set_chip(int irq, struct irq_chip *chip)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);

    if (!desc)
        return -EINVAL;

    if (!chip)
        chip = &no_irq_chip;

    desc->irq_data.chip = chip;
    irq_put_desc_unlock(desc, flags);

    return 0;
}

/**
 *	irq_set_type - set the irq trigger type for an irq
 *	@irq:	irq number
 *	@type:	IRQ_TYPE_{LEVEL,EDGE}_* value - see include/linux/irq.h
 */
int irq_set_irq_type(int irq, unsigned int type)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
    int ret = 0;

    if (!desc)
        return -EINVAL;

    ret = __irq_set_trigger(desc, type);
    irq_put_desc_busunlock(desc, flags);
    return ret;
}

/**
 *	irq_set_handler_data - set irq handler data for an irq
 *	@irq:	Interrupt number
 *	@data:	Pointer to interrupt specific data
 *
 *	Set the hardware irq controller data for an irq
 */
int irq_set_handler_data(int irq, void *data)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);

    if (!desc)
        return -EINVAL;
    desc->irq_data.handler_data = data;
    irq_put_desc_unlock(desc, flags);
    return 0;
}

/**
 *	irq_set_msi_desc_off - set MSI descriptor data for an irq at offset
 *	@irq_base:	Interrupt number base
 *	@irq_offset:	Interrupt number offset
 *	@entry:		Pointer to MSI descriptor data
 *
 *	Set the MSI descriptor entry for an irq at offset
 */
int irq_set_msi_desc_off(int irq_base, unsigned int irq_offset,
             struct msi_desc *entry)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq_base + irq_offset, &flags, IRQ_GET_DESC_CHECK_GLOBAL);

    if (!desc)
        return -EINVAL;
    desc->irq_data.msi_desc = entry;
    if (entry && !irq_offset)
        entry->irq = irq_base;
    irq_put_desc_unlock(desc, flags);
    return 0;
}

/**
 *	irq_set_msi_desc - set MSI descriptor data for an irq
 *	@irq:	Interrupt number
 *	@entry:	Pointer to MSI descriptor data
 *
 *	Set the MSI descriptor entry for an irq
 */
int irq_set_msi_desc(int irq, struct msi_desc *entry)
{
    return irq_set_msi_desc_off(irq, 0, entry);
}

/**
 *	irq_set_chip_data - set irq chip data for an irq
 *	@irq:	Interrupt number
 *	@data:	Pointer to chip specific data
 *
 *	Set the hardware irq chip data for an irq
 */
int irq_set_chip_data(int irq, void *data)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);

    if (!desc)
        return -EINVAL;
    desc->irq_data.chip_data = data;
    irq_put_desc_unlock(desc, flags);
    return 0;
}

struct irq_data *irq_get_irq_data(int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    return desc ? &desc->irq_data : NULL;
}

static int __irq_startup(struct irq_desc *desc)
{
    struct irq_data *d = irq_desc_get_irq_data(desc);
    int ret = 0;

    /* Warn if this interrupt is not activated but try nevertheless */
    WARN_ON_ONCE(irqd_irq_started(d));

    if (d->chip->irq_startup) {
        ret = d->chip->irq_startup(d);
        __irqd_clear(&desc->irq_data, IRQ_MASKED);
    } else {
        unmask_irq(desc);
    }
    __irqd_set(&desc->irq_data, IRQ_STARTED);
    return ret;
}

int irq_startup(struct irq_desc *desc)
{
    struct irq_data *d = irq_desc_get_irq_data(desc);
    int ret = 0;

    desc->depth = 0;

    if (irqd_irq_started(d)) {
        unmask_irq(desc);
    } else {
        ret = __irq_startup(desc);
    }

    return ret;
}

void irq_shutdown(struct irq_desc *desc)
{
    if (irqd_irq_started(&desc->irq_data)) {
        desc->depth = 1;
        if (desc->irq_data.chip->irq_shutdown) {
            desc->irq_data.chip->irq_shutdown(&desc->irq_data);
            __irqd_set(&desc->irq_data, IRQ_MASKED);
        } else {
            mask_irq(desc);
        }
        __irqd_clear(&desc->irq_data, IRQ_STARTED);
    }
}

static inline void mask_ack_irq(struct irq_desc *desc)
{
    if (desc->irq_data.chip->irq_mask_ack) {
        desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
        __irqd_set(&desc->irq_data, IRQ_MASKED);
    } else {
        mask_irq(desc);
        if (desc->irq_data.chip->irq_ack)
            desc->irq_data.chip->irq_ack(&desc->irq_data);
    }
}

void mask_irq(struct irq_desc *desc)
{
    if (irqd_irq_masked(&desc->irq_data))
        return;

    if (desc->irq_data.chip->irq_mask) {
        desc->irq_data.chip->irq_mask(&desc->irq_data);
        __irqd_set(&desc->irq_data, IRQ_MASKED);
        desc->depth++;
    }
}

void unmask_irq(struct irq_desc *desc)
{
    if (!irqd_irq_masked(&desc->irq_data))
        return;

    if (desc->irq_data.chip->irq_unmask) {
        desc->irq_data.chip->irq_unmask(&desc->irq_data);
        __irqd_clear(&desc->irq_data, IRQ_MASKED);
        desc->depth--;
    }
}

void irq_percpu_enable(struct irq_desc *desc, int cpu)
{
    desc->irq_data.chip->irq_unmask(&desc->irq_data);
    BUG_ON(!desc->status_percpu);
    desc->status_percpu[cpu] &= ~IRQ_MASKED;
}

void irq_percpu_disable(struct irq_desc *desc, int cpu)
{
    desc->irq_data.chip->irq_mask(&desc->irq_data);
    BUG_ON(!desc->status_percpu);
    desc->status_percpu[cpu] |= IRQ_MASKED;
}

static bool irq_may_run(struct irq_desc *desc)
{
    unsigned int mask = IRQ_INPROGRESS;

    /*
     * If the interrupt is not in progress and is not an armed
     * wakeup interrupt, proceed.
     */
    if (!irqd_has_set(&desc->irq_data, mask))
        return true;

    return false;
}

/*
 * Called unconditionally from handle_level_irq() and only for oneshot
 * interrupts from handle_fasteoi_irq()
 */
static void cond_unmask_irq(struct irq_desc *desc)
{
    /*
     * We need to unmask in the following cases:
     * - Standard level irq (IRQF_ONESHOT is not set)
     * - Oneshot irq which did not wake the thread (caused by a
     *   spurious interrupt or a primary handler handling it
     *   completely).
     */
    if (irqd_irq_masked(&desc->irq_data) && !irqd_irq_inipc(&desc->irq_data))
        unmask_irq(desc);
}

/**
 *	handle_level_irq - Level type irq handler
 *	@desc:	the interrupt description structure for this irq
 *
 *	Level type interrupts are active as long as the hardware line has
 *	the active level. This may require to mask the interrupt and unmask
 *	it after the associated handler has acknowledged the device, so the
 *	interrupt line is back to inactive.
 */
void handle_level_irq(struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
    mask_ack_irq(desc);

    if (!irq_may_run(desc))
        goto out_unlock;

    __irqd_clear(&desc->irq_data, IRQ_WAITING);

    /*
     * If its disabled or no action available
     * keep it masked and get out of here
     */
    if (unlikely(!desc->action || irqd_irq_masked(&desc->irq_data))) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        goto out_unlock;
    }

    handle_irq_event(desc);

    cond_unmask_irq(desc);

out_unlock:
    raw_spin_unlock(&desc->lock);
}

/**
 *	handle_edge_irq - edge type IRQ handler
 *	@desc:	the interrupt description structure for this irq
 *
 *	Interrupt occures on the falling and/or rising edge of a hardware
 *	signal. The occurrence is latched into the irq controller hardware
 *	and must be acked in order to be reenabled. After the ack another
 *	interrupt can happen on the same source even before the first one
 *	is handled by the associated event handler. If this happens it
 *	might be necessary to disable (mask) the interrupt depending on the
 *	controller hardware. This requires to reenable the interrupt inside
 *	of the loop which handles the interrupts which have arrived while
 *	the handler was running. If all pending interrupts are handled, the
 *	loop is left.
 */
void handle_edge_irq(struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);

    __irqd_clear(&desc->irq_data, IRQ_WAITING);

    if (!irq_may_run(desc)) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        mask_ack_irq(desc);
        goto out_unlock;
    }

    /*
     * If its disabled or no action available then mask it and get
     * out of here.
     */
    if (irqd_irq_masked(&desc->irq_data) || !desc->action) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        mask_ack_irq(desc);
        goto out_unlock;
    }

    /* Start handling the irq */
    desc->irq_data.chip->irq_ack(&desc->irq_data);

    do {
        if (unlikely(!desc->action)) {
            mask_irq(desc);
            goto out_unlock;
        }

        /*
         * When another irq arrived while we were handling
         * one, we could have masked the irq.
         * Renable it, if it was not disabled in meantime.
         */
        if (unlikely(irqd_is_pending(&desc->irq_data))) {
            if (irqd_irq_masked(&desc->irq_data))
                unmask_irq(desc);
        }

        handle_irq_event(desc);

    } while (irqd_is_pending(&desc->irq_data) &&
         !irqd_irq_masked(&desc->irq_data));

out_unlock:
    raw_spin_unlock(&desc->lock);
}

static void cond_unmask_eoi_irq(struct irq_desc *desc, struct irq_chip *chip)
{
    if (irqd_irq_masked(&desc->irq_data) && !irqd_irq_inipc(&desc->irq_data)) {
        chip->irq_eoi(&desc->irq_data);
        unmask_irq(desc);
    }
}

/**
 *	handle_fasteoi_irq - irq handler for transparent controllers
 *	@desc:	the interrupt description structure for this irq
 *
 *	Only a single callback will be issued to the chip: an ->eoi()
 *	call when the interrupt has been serviced. This enables support
 *	for modern forms of interrupt handlers, which handle the flow
 *	details in hardware, transparently.
 */
void handle_fasteoi_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = desc->irq_data.chip;

    raw_spin_lock(&desc->lock);

    if (!irq_may_run(desc))
        goto out;

    __irqd_clear(&desc->irq_data, IRQ_WAITING);

    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
    if (unlikely(!desc->action || irqd_irq_masked(&desc->irq_data))) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        mask_irq(desc);
        goto out;
    }

    handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);

    raw_spin_unlock(&desc->lock);
    return;
out:
    if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
        chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

/**
 *	handle_edge_eoi_irq - edge eoi type IRQ handler
 *	@desc:	the interrupt description structure for this irq
 *
 * Similar as the above handle_edge_irq, but using eoi and w/o the
 * mask/unmask logic.
 */
void handle_edge_eoi_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);

    raw_spin_lock(&desc->lock);

    __irqd_clear(&desc->irq_data, IRQ_PENDING);

    if (!irq_may_run(desc)) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        goto out_eoi;
    }

    /*
     * If its disabled or no action available then mask it and get
     * out of here.
     */
    if (irqd_irq_masked(&desc->irq_data) || !desc->action) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        goto out_eoi;
    }

    do {
        if (unlikely(!desc->action))
            goto out_eoi;

        handle_irq_event(desc);

    } while (irqd_is_pending(&desc->irq_data) &&
         !irqd_irq_masked(&desc->irq_data));

out_eoi:
    chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

/**
 *	handle_percpu_irq - Per CPU local irq handler
 *	@desc:	the interrupt description structure for this irq
 *
 *	Per CPU interrupts on SMP machines without locking requirements
 */
void handle_percpu_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    handle_irq_event_percpu(desc);

    if (chip->irq_eoi)
        chip->irq_eoi(&desc->irq_data);
}

/**
 * handle_percpu_devid_irq - Per CPU local irq handler with per cpu dev ids
 * @desc:	the interrupt description structure for this irq
 *
 * Per CPU interrupts on SMP machines without locking requirements. Same as
 * handle_percpu_irq() above but with the following extras:
 *
 * action->percpu_dev_id is a pointer to percpu variables which
 * contain the real device id for the cpu on which this handler is
 * called
 */
void handle_percpu_devid_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);
    struct irqaction *action = desc->action;
    int irq = irq_desc_get_irq(desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    if (likely(action)) {
        action->handler(irq, raw_cpu_ptr(action->percpu_dev_id));
    } else {
        unsigned int cpu = smp_processor_id();
        bool enabled;

        if (!irqd_is_percpu(&desc->irq_data) || !desc->status_percpu)
            enabled = false;
        else
            enabled = !(desc->status_percpu[cpu] & IRQ_MASKED);

        if (enabled)
            irq_percpu_disable(desc, cpu);

        WARN_ONCE(1, "Spurious%s percpu IRQ%u on CPU%u\n",
                enabled ? " and unmasked" : "", irq, cpu);
    }

    if (chip->irq_eoi)
        chip->irq_eoi(&desc->irq_data);
}

static void
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
             int is_chained, const char *name)
{
    if (!handle) {
        handle = handle_bad_irq;
    } else {
        struct irq_data *irq_data = &desc->irq_data;

        /*
         * With hierarchical domains we might run into a
         * situation where the outermost chip is not yet set
         * up, but the inner chips are there.  Instead of
         * bailing we install the handler, but obviously we
         * cannot enable/startup the interrupt at this point.
         */
        while (irq_data) {
            if (irq_data->chip != &no_irq_chip)
                break;
            /*
             * Bail out if the outer chip is not set up
             * and the interrupt supposed to be started
             * right away.
             */
            if (WARN_ON(is_chained))
                return;
            /* Try the parent */
            irq_data = irq_data->parent_data;
        }
        if (WARN_ON(!irq_data || irq_data->chip == &no_irq_chip))
            return;
    }

    /* Uninstall? */
    if (handle == handle_bad_irq) {
        if (desc->irq_data.chip != &no_irq_chip)
            mask_ack_irq(desc);
        __irqd_set(&desc->irq_data, IRQ_MASKED);
        if (is_chained)
            desc->action = NULL;
        desc->depth = 1;
    }
    desc->handle_irq = handle;
    desc->name = name;

    if (handle != handle_bad_irq && is_chained) {
        unsigned int type = irqd_get_trigger_type(&desc->irq_data);

        /*
         * We're about to start this interrupt immediately,
         * hence the need to set the trigger configuration.
         * But the .set_type callback may have overridden the
         * flow handler, ignoring that we're dealing with a
         * chained interrupt. Reset it immediately because we
         * do know better.
         */
        if (type != IRQ_TYPE_NONE) {
            __irq_set_trigger(desc, type);
            desc->handle_irq = handle;
        }

        __irqd_set(&desc->irq_data, IRQ_NOREQUEST);
        desc->action = &chained_action;

        if (!irqd_irq_started(&desc->irq_data))
            irq_startup(desc);
    }
}

void
__irq_set_handler(int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

    if (!desc)
        return;

    __irq_do_set_handler(desc, handle, is_chained, name);
    irq_put_desc_busunlock(desc, flags);
}

void
irq_set_chained_handler_and_data(int irq, irq_flow_handler_t handle,
                 void *data)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

    if (!desc)
        return;

    desc->irq_data.handler_data = data;
    __irq_do_set_handler(desc, handle, 1, NULL);

    irq_put_desc_busunlock(desc, flags);
}

void
irq_set_chip_and_handler_name(int irq, struct irq_chip *chip,
                  irq_flow_handler_t handle, const char *name)
{
    irq_set_chip(irq, chip);
    __irq_set_handler(irq, handle, 0, name);
}

/**
 *	handle_fasteoi_ack_irq - irq handler for edge hierarchy
 *	stacked on transparent controllers
 *
 *	@desc:	the interrupt description structure for this irq
 *
 *	Like handle_fasteoi_irq(), but for use with hierarchy where
 *	the irq_chip also needs to have its ->irq_ack() function
 *	called.
 */
void handle_fasteoi_ack_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = desc->irq_data.chip;

    raw_spin_lock(&desc->lock);

    if (!irq_may_run(desc))
        goto out;

    __irqd_clear(&desc->irq_data, IRQ_WAITING);

    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
    if (unlikely(!desc->action || irqd_irq_masked(&desc->irq_data))) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        mask_irq(desc);
        goto out;
    }

    /* Start handling the irq */
    desc->irq_data.chip->irq_ack(&desc->irq_data);

    handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);

    raw_spin_unlock(&desc->lock);
    return;
out:
    if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
        chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

/**
 *	handle_fasteoi_mask_irq - irq handler for level hierarchy
 *	stacked on transparent controllers
 *
 *	@desc:	the interrupt description structure for this irq
 *
 *	Like handle_fasteoi_irq(), but for use with hierarchy where
 *	the irq_chip also needs to have its ->irq_mask_ack() function
 *	called.
 */
void handle_fasteoi_mask_irq(struct irq_desc *desc)
{
    struct irq_chip *chip = desc->irq_data.chip;

    raw_spin_lock(&desc->lock);
    mask_ack_irq(desc);

    if (!irq_may_run(desc))
        goto out;

    __irqd_clear(&desc->irq_data, IRQ_WAITING);

    /*
     * If its disabled or no action available
     * then mask it and get out of here:
     */
    if (unlikely(!desc->action || irqd_irq_masked(&desc->irq_data))) {
        __irqd_set(&desc->irq_data, IRQ_PENDING);
        mask_irq(desc);
        goto out;
    }

    handle_irq_event(desc);

    cond_unmask_eoi_irq(desc, chip);

    raw_spin_unlock(&desc->lock);
    return;
out:
	if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
		chip->irq_eoi(&desc->irq_data);
    raw_spin_unlock(&desc->lock);
}

/**
 * irq_chip_enable_parent - Enable the parent interrupt (defaults to unmask if
 * NULL)
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_enable_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_unmask(data);
}

/**
 * irq_chip_disable_parent - Disable the parent interrupt (defaults to mask if
 * NULL)
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_disable_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_mask(data);
}

/**
 * irq_chip_ack_parent - Acknowledge the parent interrupt
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_ack_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_ack(data);
}

/**
 * irq_chip_mask_parent - Mask the parent interrupt
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_mask_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_mask(data);
}

/**
 * irq_chip_unmask_parent - Unmask the parent interrupt
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_unmask_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_unmask(data);
}

/**
 * irq_chip_eoi_parent - Invoke EOI on the parent interrupt
 * @data:	Pointer to interrupt specific data
 */
void irq_chip_eoi_parent(struct irq_data *data)
{
    data = data->parent_data;
    data->chip->irq_eoi(data);
}

/**
 * irq_chip_set_affinity_parent - Set affinity on the parent interrupt
 * @data:	Pointer to interrupt specific data
 * @dest:	The affinity mask to set
 * @force:	Flag to enforce setting (disable online checks)
 *
 * Conditinal, as the underlying parent chip might not implement it.
 */
int irq_chip_set_affinity_parent(struct irq_data *data,
                 const struct cpumask *dest, bool force)
{
    data = data->parent_data;
    if (data->chip->irq_set_affinity)
        return data->chip->irq_set_affinity(data, dest, force);

    return -ENOSYS;
}

/**
 * irq_chip_set_type_parent - Set IRQ type on the parent interrupt
 * @data:	Pointer to interrupt specific data
 * @type:	IRQ_TYPE_{LEVEL,EDGE}_* value - see include/linux/irq.h
 *
 * Conditional, as the underlying parent chip might not implement it.
 */
int irq_chip_set_type_parent(struct irq_data *data, unsigned int type)
{
    data = data->parent_data;

    if (data->chip->irq_set_type)
        return data->chip->irq_set_type(data, type);

    return -ENOSYS;
}

/**
 * irq_chip_compose_msi_msg - Componse msi message for a irq chip
 * @data:	Pointer to interrupt specific data
 * @msg:	Pointer to the MSI message
 *
 * For hierarchical domains we find the first chip in the hierarchy
 * which implements the irq_compose_msi_msg callback. For non
 * hierarchical we use the top level chip.
 */
int irq_chip_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
    struct irq_data *pos = NULL;

    for (; data; data = data->parent_data)
        if (data->chip && data->chip->irq_compose_msi_msg)
            pos = data;
    if (!pos)
        return -ENOSYS;

    pos->chip->irq_compose_msi_msg(pos, msg);

    return 0;
}
